package cn.jbolt.ndemo;

import cn.jbolt.ai.app.AppType;
import cn.jbolt.ai.app.entity.AiApp;
import cn.jbolt.ai.app.entity.AiAppNode;
import cn.jbolt.ai.app.service.AiAppConfigService;
import cn.jbolt.ai.app.service.AiAppNodeService;
import cn.jbolt.ai.app.service.AiAppService;
import cn.jbolt.ai.core.chain.config.*;
import cn.jbolt.ai.core.chain.node.NodeProviderCenter;
import cn.jbolt.util.JSONUtil;
import cn.jbolt.util.Result;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import static cn.jbolt.ndemo.DemoAppConstrant.*;

@Component
public class DemoAppIniter {

    private AiAppService aiAppService;
    private AiAppConfigService aiAppConfigService;
    private AiAppNodeService aiAppNodeService;

    public DemoAppIniter(AiAppService aiAppService, AiAppConfigService aiAppConfigService, AiAppNodeService aiAppNodeService) {
        this.aiAppService = aiAppService;
        this.aiAppConfigService = aiAppConfigService;
        this.aiAppNodeService = aiAppNodeService;
    }

    private static final Logger logger = LoggerFactory.getLogger(DemoAppIniter.class);


    @EventListener(ApplicationReadyEvent.class)
    public void init() {
        //1. 注册特殊的节点类型
        NodeProviderCenter.registerEventNodeProvider(INFO_ANALYSE_NODE_TYPE, InfoAnalyseNode.class);
        NodeProviderCenter.registerCommonNodeProvider(DATA_PROCESS_NODE_TYPE, DataProcessNode.class);
        //2. 初始化AI应用
        checkAndInitApp();
    }

    private void checkAndInitApp() {
        AiApp app = aiAppService.getById(DemoAppConstrant.APP_ID);
        if (app != null) {
            return;
        }
        app = new AiApp();
        app.setId(DemoAppConstrant.APP_ID);
        app.setBuildIn(false);
        app.setName(DemoAppConstrant.APP_NAME);
        app.setSn(DemoAppConstrant.APP_SN);
        app.setBriefInfo(DemoAppConstrant.APP_BRIE_INFO);
        app.setAppType(AppType.SIMPLE.getCode());
        app.setEnable(true);
        aiAppService.save(app);
        initAppConfig(app);
        initAppNodes(app);
    }

    /**
     * 初始化应用的节点
     *
     * @param app
     */
    private void initAppNodes(AiApp app) {
        AiAppNode analyseNode = new AiAppNode();
        analyseNode.setAppId(app.getId());
        analyseNode.setType(INFO_ANALYSE_NODE_TYPE);
        aiAppNodeService.add(analyseNode);

        AiAppNode dataProcessNode = new AiAppNode();
        dataProcessNode.setAppId(app.getId());
        dataProcessNode.setType(DATA_PROCESS_NODE_TYPE);
        aiAppNodeService.add(dataProcessNode);

        aiAppNodeService.addNodeAfters(analyseNode.getId(), "success", dataProcessNode.getId());


    }

    /**
     * 初始化应用的配置
     *
     * @param app
     */
    private void initAppConfig(AiApp app) {
        ObjectNode configs = JSONUtil.createObjectNode();
        // uiConfig
        configs.put(AppConfigConstant.CONFIG_TYPE_UI, UiAppConfig.getDefaultConfig());
        //funcCallNodeConfig
        configs.put(AppConfigConstant.CONFIG_TYPE_FUNCCALL, FuncCallNodeConfig.getDefaultConfig());
        // KlbSearchNodeConfig
        KlbSearchNodeConfig klbSearchNodeConfig = new KlbSearchNodeConfig();
        klbSearchNodeConfig.setOutputField("klb_search_result");
        klbSearchNodeConfig.setInputField(AppConfigConstant.ATTR_KEY_PROMPT);
        configs.put(AppConfigConstant.CONFIG_TYPE_KLB_SEARCH, JSONUtil.toJsonObject(klbSearchNodeConfig));
        //llmConfig
        LlmConfig defaultConfig = new LlmConfig();
        defaultConfig.setStream(false);
        defaultConfig.setMaxTokens(4000);
        defaultConfig.setPersistDb(true);
        defaultConfig.setInputField(AppConfigConstant.INPUT_FROM_CUSTOM);
        defaultConfig.setPromptTpl("请根据我提供的血常规检测报告图片，一步一步严格按照以下步骤执行:\n\n1.请从图片中识别所有血常规检测项目。对于每个检测项目提取以下信息:\n - 检查项目名称\n - 检测结果，要求必须仅提取数字\n - 参考区间，要求提取出最小值和最大值,，仅提取数字不含单位\n - 单位，数值的单位要单独提取出来\n\n2. 将以上提取的信息按以下格式整理：\n [\n{\n\"name\": \"提取的检查项名称\",\n\"result\": \"提取的检查结果\",\n\"min\": 提取的参考范围最小值，没有按0算,\n\"max\": 提取的参考范围最大值,\n\"unit\": \"提取的单位\"\n}\n]\n\n\n2.请您作为经验丰富的专业医生，从以下7个方面根据报告的参考区间和检查结果进行专业解读和建议:\n\n - 感染情况\n - 贫血状况\n - 血液系统疾病\n - 炎症和免疫反应\n - 出血或凝血问题\n - 其他系统疾病\n - 综合以上6个方面，进行一个总体汇报总结\n\n\n3. 请将所有分析结果整理为以下JSON格式,直接输出JSON内容:\n{\n\"list\": [\n{\n\"name\": \"提取的检查项名称\",\n\"result\": \"提取的检查结果\",\n\"min\": 提取的参考范围最小值，没有按0算,\n\"max\": 提取的参考范围最大值,\n\"unit\": \"提取的单位\"\n}\n],\n\"detailedAnalysis\": [\n {\"name\":\"感染\", \"content\":\"分析和建议\"},\n  {\"name\":\"贫血\", \"content\":\"分析和建议\"},\n  {\"name\":\"血液系统疾病\", \"content\":\"分析和建议\"},\n  {\"name\":\"炎症和免疫反应\", \"content\":\"分析和建议\"},\n  {\"name\":\"出血或凝血问题\", \"content\":\"分析和建议\"},\n {\"name\":\"其他系统疾病\", \"content\":\"分析和建议\"}\n],\n\"summary\":\"总体分析和建议\"\n}\n注意:\n\n请仅输出JSON格式的分析结果\n检查的pass项必须再三审核是否正确\n分析报告必须依据检查结果和参考区间，给出准确详尽的说明和分析、建议\n不要使用markdown代码块\n不要包含任何其他说明文字");
        defaultConfig.setSystemRole("你是一个从业多年，经验丰富，专业的临床医生，你需要从提供的检查报告中，认真仔细地审阅，获取数据，并给出专业的分析报告。\n以下是生成检查报告的参考资料：\n血常规项目主要包含三大类：白细胞、红细胞和血小板。血液中的白细胞有五种，按照体积从小到大分别是淋巴细胞、嗜碱性粒细胞、中性粒细胞、嗜酸性粒细胞和单核细胞。白细胞可吞噬病菌、中和病菌分泌的毒素等。红细胞没有细胞核，含血红蛋白，产生在红骨髓中，作用是把氧气输送到各组织，并把二氧化碳带到肺泡内。血小板比红细胞和白细胞小，形状不规则，没有细胞核，主要功能是凝血和止血，修补破损的血管。\n检验结果异常的原因可分为生理性原因和疾病原因。生理性因素导致的结果异常往往是轻度异常（异常值与临界值的差低于参考区间差的一半）。异常值与临界值的差超过参考区间差一半的，往往提示有疾病。\n白细胞计数偏高（成人>9.5×10^9/L，儿童>12×10^9/L，婴儿>20×10^9/L）往往提示细菌感染，但不能排除病毒感染（如乙脑病毒）。剧烈运动、情绪紧张、饱餐后抽血也有可能导致白细胞计数偏高（轻微）。白细胞计数偏低，往往提示再生障碍性贫血、脾功能亢进、病毒感染等疾病。服用氯霉素等药物也会导致白细胞计数偏低。通常情况下，白细胞计数为（15~18）×10^9/L，提示中度感染；当白细胞计数超过18×10^9/L时，往往是严重感染。\n白细胞计数严重偏高（>24×10^9/L），同时伴有血红蛋白浓度和血小板计数明显偏低，往往提示白血病。\n中性粒细胞百分比或中性粒细胞绝对值偏高，往往提示细菌感染，也有可能是乙脑病毒感染。中性粒细胞百分比或中性粒细胞绝对值偏低，往往提示病毒感染或再生障碍性贫血。某些药物也可能导致中性粒细胞百分比或中性粒细胞绝对值偏低。\n淋巴细胞百分率升高，白细胞计数、中性粒细胞正常或偏低，通常是病毒感染所致。\n淋巴细胞百分率升高，白细胞计数、中性粒细胞百分比升高，通常是病毒与细菌合并感染所致。\n淋巴细胞总数（绝对值）偏高，可能是病毒感染或某些血液疾病所致；偏低可能是糖皮质激素类药物所致，也有可能是病毒感染所致。\n单核细胞比值或单核细胞绝对数偏高，可能是病毒感染（如EB病毒）或结核分枝杆菌感染所致。正常儿童单核细胞比值会比成人的稍高。\n嗜酸性粒细胞绝对数或百分率偏高，大多是过敏性疾病或寄生虫感染所致。嗜酸性粒细胞绝对值或百分率偏低，可能是伤寒、烧伤或大手术的应激状态。长期使用肾上腺皮质激素也会导致嗜酸性粒细胞绝对数或百分率偏低。\n嗜碱性粒细胞绝对值或百分比偏高可能与过敏性疾病、感染性疾病、自身免疫性疾病、肿瘤性疾病等因素有关。嗜碱性粒细胞绝对值或百分比偏低通常没有临床意义。\n红细胞计数（正常范围4.0~5.5×10^12/L）或血红蛋白浓度轻度偏高，可能是缺水或缺氧所致；如果明显偏高，可能是心脏、肾脏或肿瘤方面疾病所致。红细胞计数或血红蛋白浓度偏低，表示贫血。\n平均红细胞血红蛋白含量偏低，提示有贫血或慢性感染。\n平均红细胞血红蛋白浓度偏低，提示有贫血。\n平均红细胞体积、平均红细胞血红蛋白含量和平均红细胞血红蛋白浓度同时偏低，通常是缺铁性贫血或地中海贫血所致。\n有核红细胞百分比或有核红细胞计数偏高可能是溶血性贫血、失血性贫血、巨幼红细胞性贫血、白血病等疾病所致。\n血小板计数偏高，常见于急慢性炎症、缺铁性贫血、骨髓增生性疾病等。血小板计数偏低，常见于再生障碍性贫血、急性白血病、肝硬化（失代偿期）、系统性红斑狼疮等疾病。\n血小板压积偏高且同时伴有血小板计数偏高，有可能是病毒感染所致。剧烈运动、寒冷等非疾病因素也可能导致血小板压积偏高。\n全血超敏C反应蛋白（hs-CRP）数值可以更加直观地反映患者的炎症情况。全血超敏C反应蛋白数值偏高，往往提示存在炎症，通常是细菌感染所致，轻度偏高也有可能是病毒感染所致。");
        defaultConfig.setUseContext(false);
        defaultConfig.setTemperature(0.2);
        defaultConfig.setOutputToResponse(true);
        configs.put(AppConfigConstant.CONFIG_TYPE_LLM, JSONUtil.toJsonObject(defaultConfig));
        //klbChatConfig
        KlbChatNodeConfig klbChatNodeConfig = new KlbChatNodeConfig();
        klbChatNodeConfig.setKlbResultField("klb_search_result");
        klbChatNodeConfig.setNoMatchReply("抱歉您的问题无法回答");
        klbChatNodeConfig.setOnlyKlb(false);
        configs.put(AppConfigConstant.CONFIG_TYPE_KLB_CHAT, JSONUtil.toJsonObject(klbChatNodeConfig));
        Result result = aiAppConfigService.add(AppConfigConstant.LINK_TYPE_APP, app.getId(), configs);
        if (result.isSuccess()) {
             logger.info("应用初始化成功");
        } else {
            logger.error("应用初始化成功");
        }

    }
}